﻿using System.Net.Sockets;
using System;
using UnityEngine;
using System.Text;
using System.IO;
using Google.Protobuf;

namespace HBattle
{
    public class NetworkManager : SingletonManager<NetworkManager>
    {
        public NetworkManager() { }

        private const int MAX_READ = 8192; 
        private TcpClient tcpClient;
        private byte[] byteBuffer = new byte[MAX_READ];

        public void SendMessage(string msg)
        {
            byte[] sendBytes = OnEncode(msg);
            tcpClient.GetStream().BeginWrite(sendBytes, 0, sendBytes.Length, new AsyncCallback(OnWrite), null);
        }

        public void SendMessage(int protobufId, IMessage msg)
        {
            byte[] sendBytes = OnEncode(protobufId, msg);
            tcpClient.GetStream().BeginWrite(sendBytes, 0, sendBytes.Length, new AsyncCallback(OnWrite), null);
        }

        public void ConnectServer(string ip, int port)
        {
            CloseConnect();
            tcpClient = new TcpClient(AddressFamily.InterNetwork);
            tcpClient.SendTimeout = 1000;
            tcpClient.ReceiveTimeout = 1000;
            tcpClient.NoDelay = true;
            tcpClient.BeginConnect(ip, port, new AsyncCallback(OnConnect), null); 
        }

        public void CloseConnect()
        {
            if (tcpClient == null)
            {
                return;
            }

            if (tcpClient.Connected)
            {
                NetworkStream ns = tcpClient.GetStream();
                ns.Close();
            }

            tcpClient.Close();
            tcpClient = null;
        }

        public void Start()
        {
            ConnectServer("127.0.0.1", 10111);
        }

        public void Update()
        {

        }

        public void Destroy()
        {
            CloseConnect();
        }

        private void OnConnect(IAsyncResult ar)
        {
            if (!tcpClient.Connected)
            {
                CloseConnect();
                Debug.LogError("Connect Failed");
                return;
            }
            tcpClient.EndConnect(ar);

            Listen();
        }

        private void Listen()
        {
            Array.Clear(byteBuffer, 0, byteBuffer.Length);
            tcpClient.GetStream().BeginRead(byteBuffer, 0, MAX_READ, new AsyncCallback(OnRead), tcpClient);
        }

        private void OnRead(IAsyncResult ar)
        {
            int bytesRead = tcpClient.GetStream().EndRead(ar);
            if (bytesRead == 0)
            {
                Debug.LogError("OnRead Error");
                CloseConnect();
                return;
            }
            OnDecode(bytesRead);
            Listen();
        }

        private void OnDecode(int dataLength)
        {
            int pointer = 0;
            while(pointer < dataLength)
            {
                int length = byteBuffer[pointer++] << 8 | byteBuffer[pointer++];

                string msg = Encoding.UTF8.GetString(byteBuffer, pointer, length);
                pointer += length;

                Debug.Log("Receive msg:" + msg);
            }



        }

        private byte[] OnEncode(string msg)
        {
            Debug.Log("Send " + msg);
            byte[] sendBytes = Encoding.UTF8.GetBytes(msg);
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    ushort length = (ushort)(sendBytes.Length);
                    if (BitConverter.IsLittleEndian)
                    {
                        length = (UInt16)((0x00FF & (length >> 8)) | (0xFF00 & (length << 8)));
                    }

                    bw.Write(length);
                    bw.Write(sendBytes);
                }
                return ms.ToArray();
            }
        }

        private byte[] OnEncode(int msgId, IMessage msg)
        {
            Debug.Log("Send " + msg.ToString());
            byte[] sendBytes = msg.ToByteArray();
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    ushort length = (ushort)(sendBytes.Length + 2);
                    ushort ushortMsgId = (ushort)msgId;
                    if (BitConverter.IsLittleEndian)
                    {
                        length = (UInt16)((0x00FF & (length >> 8)) | (0xFF00 & (length << 8)));
                        ushortMsgId = (UInt16)((0x00FF & (ushortMsgId >> 8)) | (0xFF00 & (ushortMsgId << 8)));
                    }

                    bw.Write(length);
                    bw.Write(ushortMsgId);
                    bw.Write(sendBytes);
                }
                return ms.ToArray();
            }
        }

        private void OnWrite(IAsyncResult ar)
        {
            tcpClient.GetStream().EndWrite(ar);
        }
    }
}


